package com.xlh.lock;

import com.google.common.collect.Maps;
import com.xlh.constant.LockConstant;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 1.可重入锁，2.锁带过期时间
 *
 * @author cheer
 */
public class ReentrantKeyLockImpl implements KeyLock<String> {

    @Data
    @AllArgsConstructor
    private static class LockInfo {
        private ReentrantLock lock;
        private Long timestamp;
    }

    /**
     * 全局已分配的锁
     */
    private final ConcurrentMap<String, LockInfo> globalLockMap = new ConcurrentHashMap<>();

    /**
     * 当前线程持有的锁
     */
    private final ThreadLocal<Map<String, LockInfo>> localLockMap = ThreadLocal.withInitial(Maps::newHashMap);

    @Override
    public void lock(String key) {
        if (preTryLock(key)) return;
        LockInfo global;

        // 从全局锁map中获取锁
        global = globalLockMap.getOrDefault(key, new LockInfo(new ReentrantLock(), System.currentTimeMillis()));
        global.getLock().lock();
        localLockMap.get().put(key, global);
    }

    @Override
    public boolean tryLock(String key) {
        if (preTryLock(key)) {
            return true;
        }

        LockInfo global = globalLockMap.get(key);
        if (global.getLock().tryLock()) {
            localLockMap.get().put(key, global);
            return true;
        }
        return false;
    }

    public boolean isLocked(String key) {
        return false;
    }

    @Override
    public boolean tryLock(String key, long timeout, TimeUnit timeUnit) {
        if (preTryLock(key)) {
            return true;
        }

        try {
            LockInfo global = globalLockMap.get(key);
            if (global.getLock().tryLock(timeout, timeUnit)) {
                localLockMap.get().put(key, global);
                return true;
            }
        } catch (InterruptedException ignore) {
        }
        return false;
    }

    @Override
    public void unlock(String key) {
        if (key == null) {
            return;
        }

        LockInfo global = globalLockMap.get(key);
        if (global == null) {
            localLockMap.get().remove(key);
            return;
        }

        ReentrantLock globalLock = global.getLock();
        if (globalLock.isHeldByCurrentThread()) {
            globalLock.unlock();
            if (!globalLock.isHeldByCurrentThread()) {
                localLockMap.get().remove(key);
            }
            if (!globalLock.hasQueuedThreads()) {
                globalLockMap.remove(key, global);
            }
        }
    }

    public void removeLock(String key){

    }

    private boolean preTryLock(String key) {
        if (key == null) {
            return true;
        }

        if (getLocalLock(key)) {
            return true;
        }

        LockInfo global = getGlobalLock(key);
        if (global == null) {
            return true;
        }

        boolean expired = System.currentTimeMillis() - global.getTimestamp() >= LockConstant.EXPIRE_TIME;
        if (expired && expireLock(key, global)) {
            return true;
        }
        return false;
    }

    private boolean getLocalLock(String key) {
        LockInfo local = localLockMap.get().get(key);
        if (local != null) {
            local.getLock().lock();
            return true;
        }
        return false;
    }

    private LockInfo getGlobalLock(String key) {
        LockInfo global = globalLockMap.get(key);
        if (global == null) {
            LockInfo local = new LockInfo(new ReentrantLock(), System.currentTimeMillis());
            local.getLock().lock();
            LockInfo current = globalLockMap.putIfAbsent(key, local);
            if (current == null) {
                localLockMap.get().put(key, local);
            } else {
                local.getLock().unlock();
                global = current;
            }
        }
        return global;
    }

    private boolean expireLock(String key, LockInfo global) {
        LockInfo local = new LockInfo(new ReentrantLock(), System.currentTimeMillis());
        local.getLock().lock();
        if (globalLockMap.replace(key, global, local)) {
            localLockMap.get().put(key, local);
            return true;
        }
        local.getLock().unlock();
        return false;
    }
}
